/*
 * Copyright 2014 - Present Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.easyaop.api.around.annotation;

import static net.bytebuddy.matcher.ElementMatchers.named;

import com.easyaop.api.around.Call;
import com.easyaop.api.support.InvokeDynamicProxy;

import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.annotation.AnnotationValue;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeVariableToken;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.InvokeDynamic;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaConstant;
import net.bytebuddy.utility.JavaType;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.Collections;

import com.easyaop.api.around.CustomCall;

public @interface CustomCallImpl {

    enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<CustomCall> {
        INSTANCE;

        private static final MethodDescription.InDefinedShape FALLBACK_TO_DEFAULT;

        private static final MethodDescription.InDefinedShape NULL_IF_IMPOSSIBLE;

        private static final MethodDescription.InDefinedShape CUSTOM_BOOTSTRAP;

        private static final MethodDescription CALL;

        public Class<CustomCall> getHandledType() {
            return CustomCall.class;
        }

        public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<CustomCall> annotation,
                                                               MethodDescription source,
                                                               ParameterDescription target,
                                                               Implementation.Target implementationTarget,
                                                               Assigner assigner,
                                                               Assigner.Typing typing) {
            TypeDescription targetType = target.getType().asErasure();
            if (!targetType.represents(Call.class) || source.isConstructor()) {
                throw new IllegalStateException("A super method call proxy can only be assigned to Runnable or " +
                        "Callable types: " + target);
            }
            //使用java7之后新增的字节码指令InvokeDynamic，在运行时动态地确定方法调用的目标
            InvokeDynamic idy = InvokeDynamic
                    //引导方法负责在运行时动态地创建一个 CallSite 对象。CallSite 是一个包含方法句柄的对象，它决定了最终调用的方法
                    .bootstrap(
                            //自定义引导方法
                            CUSTOM_BOOTSTRAP,
                            //方法句柄的类型
                            JavaConstant.MethodType.ofSignature(CALL),
                            //原方法的方法句柄
                            JavaConstant.MethodHandle.of(source.asDefined()),
                            //调用的方法类型
                            JavaConstant.MethodType.ofSignature(CALL))
                    //指定最终调用的方法的名称和声明类型
                    .invoke(
                            CALL.getInternalName(),
                            //获取目标参数的原始类型,避免因为泛型参数的不同而导致类型检查失败
                            CALL.getDeclaringType().asErasure());
            if (!source.isStatic()) {
                //非静态方法必须传递this对象
                idy = idy.withThis(source.getDeclaringType().asErasure());
            }
            idy = idy.withMethodArguments();
            InvokeDynamicProxy proxy = InvokeDynamicProxy.wrap(idy);
            return new MethodDelegationBinder.ParameterBinding
                    //创建一个匿名的参数绑定对象
                    .Anonymous(proxy
                    //根据传入的参数生成一个栈操作对象，该对象包含了执行动态调用所需的字节码指令
                    .getStackManipulation(source, assigner, typing));
        }

        static {
            MethodList<MethodDescription.InDefinedShape> methods =
                    TypeDescription.ForLoadedType.of(CustomCall.class).getDeclaredMethods();
            FALLBACK_TO_DEFAULT = methods.filter(named("fallbackToDefault")).getOnly();
            NULL_IF_IMPOSSIBLE = methods.filter(named("nullIfImpossible")).getOnly();
            CUSTOM_BOOTSTRAP = new MethodDescription.Latent(
                    //潜在的类型描述（类）
                    new TypeDescription.Latent(
                            "com.easyaop.api.support.CustomMetaFactory",
                            Opcodes.ACC_PUBLIC,
                            TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class)),
                    //方法名称
                    "metaFactory",
                    //定义引导方法的访问权限和调用方式，静态方法可以在不创建类实例的情况下直接调用，公共方法可以被外部类访问
                    Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC,
                    //方法的类型变量列表。这里使用空列表表示该方法没有类型变量
                    Collections.<TypeVariableToken>emptyList(),
                    //引导方法的返回类型
                    JavaType.CALL_SITE.getTypeStub().asGenericType(),
                    //引导方法的参数列表
                    Arrays.asList(
                            //用于查找方法句柄
                            new ParameterDescription.Token(
                                    JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().asGenericType()),
                            //通常表示调用点的名称
                            new ParameterDescription.Token(
                                    TypeDescription.ForLoadedType.of(String.class).asGenericType()),
                            //用于描述调用点的方法类型
                            new ParameterDescription.Token(
                                    JavaType.METHOD_TYPE.getTypeStub().asGenericType()),
                            //传递额外的方法类型信息
                            new ParameterDescription.Token(
                                    JavaType.METHOD_TYPE.getTypeStub().asGenericType()),
                            //代表一个方法句柄
                            new ParameterDescription.Token(
                                    JavaType.METHOD_HANDLE.getTypeStub().asGenericType()),
                            //进一步指定方法类型
                            new ParameterDescription.Token(
                                    JavaType.METHOD_TYPE.getTypeStub().asGenericType())),
                    //方法抛出的异常类型列表。这里使用空列表表示该方法不抛出任何异常
                    Collections.<TypeDescription.Generic>emptyList(),
                    //方法的注解列表。这里使用空列表表示该方法没有任何注解
                    Collections.<AnnotationDescription>emptyList(),
                    //方法的默认注解值。这里使用 AnnotationValue.UNDEFINED 表示没有默认注解值
                    AnnotationValue.UNDEFINED,
                    //方法的签名类型。这里使用 TypeDescription.Generic.UNDEFINED 表示没有指定签名类型
                    TypeDescription.Generic.UNDEFINED);
            //编译Call类的方法图,用于分析类的继承关系和方法调用图
            CALL = MethodGraph.Compiler.Default
                    .forJVMHierarchy()
                    .compile(TypeDefinition.Sort.describe(Call.class))
                    .listNodes().asMethodList()
                    //获取筛选后的唯一抽象方法
                    .filter(ElementMatchers.isAbstract()).getOnly();
        }
    }
}
